﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Drawing;
using System.Reflection;
using System.Windows.Forms;

namespace System.ComponentModel.Design;

internal sealed partial class DesignerActionPanel
{
    private sealed class MethodLine : Line
    {
        private DesignerActionList? _actionList;
        private DesignerActionMethodItem? _methodItem;
        private readonly MethodItemLinkLabel _linkLabel;
        private MethodLine(IServiceProvider serviceProvider, DesignerActionPanel actionPanel) : base(serviceProvider, actionPanel)
        {
            _linkLabel = new MethodItemLinkLabel
            {
                ActiveLinkColor = ActionPanel.ActiveLinkColor,
                AutoSize = false,
                BackColor = Color.Transparent,
                LinkBehavior = LinkBehavior.HoverUnderline,
                LinkColor = ActionPanel.LinkColor,
                TextAlign = ContentAlignment.MiddleLeft,
                UseMnemonic = false,
                VisitedLinkColor = ActionPanel.LinkColor
            };

            _linkLabel.LinkClicked += OnLinkLabelLinkClicked;
            AddedControls.Add(_linkLabel);
        }

        // _methodItem and _actionList are set in UpdateActionItem, which is always called right after the MethodLine object creation
        public override string FocusId => $"METHOD:{_actionList!.GetType().FullName}.{_methodItem!.MemberName}";

        public override void Focus()
        {
            _linkLabel.Focus();
        }

        public override Size LayoutControls(int top, int width, bool measureOnly)
        {
            Size linkLabelSize = _linkLabel.GetPreferredSize(new Size(int.MaxValue, int.MaxValue));
            if (!measureOnly)
            {
                _linkLabel.Location = new Point(LineLeftMargin, top + LineVerticalPadding / 2);
                _linkLabel.Size = linkLabelSize;
            }

            return linkLabelSize + new Size(LineLeftMargin + LineRightMargin, LineVerticalPadding);
        }

        private void OnLinkLabelLinkClicked(object? sender, LinkLabelLinkClickedEventArgs e)
        {
            Debug.Assert(!ActionPanel.InMethodInvoke, "Nested method invocation");
            ActionPanel.InMethodInvoke = true;
            try
            {
                // _methodItem is set in UpdateActionItem, which is always called right after the MethodLine object creation
                _methodItem!.Invoke();
            }
            catch (Exception ex)
            {
                if (ex is TargetInvocationException)
                {
                    ex = ex.InnerException!;
                }

                // NOTE: We had code to rethrow if this was one of [NullReferenceException, StackOverflowException, OutOfMemoryException,
                // ThreadAbortException]. Removing this rethrow. StackOverflow and ThreadAbort can't be meaningfully caught, and
                // NullRef and OutOfMemory really shouldn't be caught. Out of these, OOM is the most correct one to call, but OOM is
                // thrown by GDI+ for pretty much any problem, so isn't reliable as an actual indicator that you're out of memory. If
                // you really are out of memory, it's very likely you'll get another OOM shortly.
                ActionPanel.ShowError(string.Format(SR.DesignerActionPanel_ErrorInvokingAction, _methodItem!.DisplayName, Environment.NewLine + ex.Message));
            }
            finally
            {
                ActionPanel.InMethodInvoke = false;
            }
        }

        internal override void UpdateActionItem(LineInfo lineInfo, ToolTip toolTip, ref int currentTabIndex)
        {
            Info info = (Info)lineInfo;
            _actionList = info.List;
            _methodItem = info.Item;
            toolTip.SetToolTip(_linkLabel, _methodItem.Description);
            _linkLabel.Text = StripAmpersands(_methodItem.DisplayName);
            _linkLabel.AccessibleDescription = _methodItem.Description;
            _linkLabel.TabIndex = currentTabIndex++;
        }

        private sealed class MethodItemLinkLabel : LinkLabel
        {
            protected override bool ProcessDialogKey(Keys keyData)
            {
                if ((keyData & Keys.Control) == Keys.Control)
                {
                    Keys keyCode = keyData & Keys.KeyCode;
                    switch (keyCode)
                    {
                        case Keys.Tab:
                            // We specifically ignore Ctrl+Tab because it prevents the window switcher dialog
                            // from showing up in VS. Normally the key combination is only needed
                            // when a LinkLabel contains multiple links, but that can't happen
                            // inside the DesignerActionPanel.
                            return false;
                    }
                }

                return base.ProcessDialogKey(keyData);
            }
        }

        public static StandardLineInfo CreateLineInfo(DesignerActionList list, DesignerActionMethodItem item) => new Info(list, item);

        private sealed class Info(DesignerActionList list, DesignerActionMethodItem item) : StandardLineInfo(list)
        {
            public override DesignerActionMethodItem Item { get; } = item;
            public override Line CreateLine(IServiceProvider serviceProvider, DesignerActionPanel actionPanel)
            {
                return new MethodLine(serviceProvider, actionPanel);
            }

            public override Type LineType => typeof(MethodLine);
        }
    }
}
